package com.ml.redis.lock;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁示例
 *
 * @author xr-8
 * @date 2021-08-18
 * @time 20:43
 */
@Slf4j
public class SimpleLock {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    Redisson redisson;

    //分布式锁示例
    @RequestMapping(" /deduct_ stock")
    public String deductStock() {
        String lockKey = "product_ 101";
        //Boolean result = stringRedisTemplate.opsForValue().setIfAbsent (lockKey, zhuue ) ; //iedis.setnx(k, v)
        //stringRedisTemplate. expire(lockKey, 10, TimeUnit. SECONDS) ;
        String clientId = UUID.randomUUID().toString();//增加线程标识
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);//增加超时时间
        if (!result) {
            return "error_code";
        }
        try {
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock"))); //jedis.get("stock" )
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); //jedis.set(key, value)
                System.out.println("扣减成功，剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {//判断是否当前线程获取的锁
                stringRedisTemplate.delete(lockKey);
                return "end";
            }
        }
        return "end";
    }

    /**
     * redisson实现的分布式锁
     *
     * @return
     */
    @RequestMapping(" /deduct_stock")
    public String deductStock2() {
        String lockKey = "product_101";
        //Boolean result = stringRedisTemplate.opsForValue().setIfAbsent (lockKey, zhuue ) ; //iedis.setnx(k, v)
        //stringRedisTemplate. expire(lockKey, 10, TimeUnit. SECONDS) ;
        /*String clientId = UUID.randomUUID().toString();//增加线程标识
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);//增加超时时间
        if (!result) {
            return "error_code";
        }*/
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            redissonLock.lock();
            int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));//jedis.get("stock");
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); //jiedis.set(key, value)
                System.out.println("扣减成功，剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            redissonLock.unlock();
            /*if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){//判断是否当前线程获取的锁
                stringRedisTemplate.delete(lockKey);
            }*/
        }
        return "end";
    }

    /**
     * redisson的redlock实现的分布式锁
     *
     * @return
     */
    @RequestMapping("/redlock")
    public String redlock() {
        String lockKey = "product_001";
        //这里需要自己实例化不同redis实例的redisson客户端连接，这里只是伪代码用一个redisson客户端简化了
        RLock lock1 = redisson.getLock(lockKey);
        RLock lock2 = redisson.getLock(lockKey);
        RLock lock3 = redisson.getLock(lockKey);
        //根据多个RLock对象构建RedissonRedLock（最核心的差别就在这里）
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        try {
            //waitTimeout 尝试获取锁的最大等待时间，超过这个值，则认为获取锁失败
            //leaseTime锁的持有时间,超过这个时间锁会自动失效（值应设置为大于业务处理的时间，确保在锁有效期内业务能处理录
            boolean res = redLock.tryLock(10, 30, TimeUnit.SECONDS);
            if (res) {
                //成功获得锁,在这里处理业务
            }
        } catch (Exception e) {
            throw new RuntimeException("lock fail");
        } finally {
            //无论如何，最后都要解锁
            redLock.unlock();
        }
        return "end";
    }

    /**
     * 秒杀设计-这个设计是基于减缓存和减数据库内数据是同步操作
     * @param productId
     * @return
     */
    @PostMapping(" / {productId}")
    public String seckill(@PathVariable("productId") Long productId) {
        //jedis.decr()
        Long stock = stringRedisTemplate.opsForValue().decrement("key" + productId);
        if (stock < 0) {
            stringRedisTemplate.opsForValue().increment("key" + productId);//这一步是防止少卖，细细理解下
            return "商品已售完";
        }
        try {
            //orderService.seckill(productId);执行秒杀减库存（先减缓存，再减数据库内的数据）
        } catch (Exception e) {
            //jedis.incr()
            stringRedisTemplate.opsForValue().increment("key" + productId);
            log.error("创建订单失败", e);
            return "创建订单失败:" + e.getMessage();
        }
        return "end";
    }
}